<!doctype html>
<html lang="en">
<head>
  <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
  <style>
    html, body {
      width: 100%;
      height: 100%;
    }

    body {
      margin: 0;
      padding: 0;
      -moz-user-select: none;
    }

    .view {
      position: relative;
      width: 100%;
      height: 100%;
      overflow: hidden;
    }

    .tips {
      position: relative;
      text-align: center;
      z-index: 999;
    }

    #cas {
      position: absolute;
      display: block;
      top: 0;
      left: 0;
      right: 0;
      margin: auto;
      cursor: move;
    }
  </style>
  <title>3d多立方体</title>
</head>
<body onselectstart="return false">
<div class="view">
  <p class="tips">鼠标拖动立方体转动，滚动鼠标滚轮缩放~</p>
  <canvas id="cas" width="700" height="600">您的浏览器不支持canvas</canvas>
</div>

<script src="../public/stats.min.js"></script>
<script>
  'use strict';

  var stats = new Stats();
  stats.setMode(0);
  stats.domElement.style.position = 'absolute';
  stats.domElement.style.right = '0px';
  stats.domElement.style.top = '0px';
  document.body.appendChild(stats.domElement);

  var canvas = document.getElementById("cas");

  canvas.width = document.body.offsetWidth;
  canvas.height = document.body.offsetHeight;

  var ctx = canvas.getContext("2d");
  var fallLength = 1000,
      centerX = canvas.width / 2,
      centerY = canvas.height / 2,
      cubeWidth = 100;

  window.RAF = (function() {
    return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) {window.setTimeout(callback, 1000 / 60); };
  })();

  var Cube = function(opt) {
    //存放所有点坐标
    this.dots = [];

    //存放面
    this.faces = [];

    this.init(opt);
  };

  Cube.prototype = {
    init: function(opt) {
      this.xpos = opt.x;
      this.ypos = opt.y;
      this.zpos = opt.z;
      this.w = opt.w;

      for (var i = 0; i < 8; i++) {
        this.dots.push({ x: 0, y: 0, z: 0 })
      }

      this.setVector();

      this.faces.push([this.dots[0], this.dots[1], this.dots[3], this.dots[2]]);
      this.faces.push([this.dots[2], this.dots[3], this.dots[5], this.dots[4]]);
      this.faces.push([this.dots[4], this.dots[5], this.dots[7], this.dots[6]]);
      this.faces.push([this.dots[6], this.dots[7], this.dots[1], this.dots[0]]);
      this.faces.push([this.dots[1], this.dots[3], this.dots[5], this.dots[7]]);
      this.faces.push([this.dots[0], this.dots[2], this.dots[4], this.dots[6]]);
    },

    setVector: function() {
      var x = this.xpos,
          y = this.ypos,
          z = this.zpos,
          dis = this.w / 2;

      this.dots[0].x = x - dis;
      this.dots[0].y = y - dis;
      this.dots[0].z = z + dis;

      this.dots[1].x = x - dis;
      this.dots[1].y = y + dis;
      this.dots[1].z = z + dis;

      this.dots[2].x = x + dis;
      this.dots[2].y = y - dis;
      this.dots[2].z = z + dis;

      this.dots[3].x = x + dis;
      this.dots[3].y = y + dis;
      this.dots[3].z = z + dis;

      this.dots[4].x = x + dis;
      this.dots[4].y = y - dis;
      this.dots[4].z = z - dis;

      this.dots[5].x = x + dis;
      this.dots[5].y = y + dis;
      this.dots[5].z = z - dis;

      this.dots[6].x = x - dis;
      this.dots[6].y = y - dis;
      this.dots[6].z = z - dis;

      this.dots[7].x = x - dis;
      this.dots[7].y = y + dis;
      this.dots[7].z = z - dis;
    },

    move: function(xc, yc, zc) {
      this.xpos += xc;
      this.ypos += yc;
      this.zpos += zc;

      this.dots.forEach(function(dot) {
        dot.x += xc;
        dot.y += yc;
        dot.z += zc;
      })
    },

    rotate: function(angleX, angleY) {
      var xcos = Math.cos(angleX);
      var xsin = Math.sin(angleX);
      var ycos = Math.cos(angleY);
      var ysin = Math.sin(angleY);

      var x, y, z;
      var nx, ny, nz;
      var xa = 0, ya = 0, za = 0;

      //计算旋转后的所有点坐标位置
      this.dots.forEach(function(dot) {
        x = dot.x;
        y = dot.y;
        z = dot.z;

        //绕X轴旋转
        ny = y * xcos - z * xsin;
        nz = z * xcos + y * xsin;

        //绕Y轴旋转
        nx = x * ycos - nz * ysin;
        nz = nz * ycos + x * ysin;

        dot.x = nx;
        dot.y = ny;
        dot.z = nz;

        xa += nx;
        ya += ny;
        za += nz;
      });

      //更新中心点坐标
      this.xpos = xa / this.dots.length;
      this.ypos = ya / this.dots.length;
      this.zpos = za / this.dots.length;
    }
  };

  function drawFace(face) {
    if (!face.color) {
      face.color = "rgba(" + parseInt(getRandom(128, 255)) + "," + parseInt(getRandom(128, 255)) + "," + parseInt(getRandom(128, 255)) + ",1)";
    }

    ctx.save();
    ctx.beginPath();
    ctx.moveTo(get2d(face[0]).x, get2d(face[0]).y);
    ctx.lineTo(get2d(face[1]).x, get2d(face[1]).y);
    ctx.lineTo(get2d(face[2]).x, get2d(face[2]).y);
    ctx.lineTo(get2d(face[3]).x, get2d(face[3]).y);
    ctx.closePath();
//
//        ctx.strokeStyle = "#fff";
//        ctx.stroke();

    ctx.fillStyle = face.color;
    ctx.fill();
  }

  function get2d(dot) {
    var scale = fallLength / (fallLength + dot.z);
    var x = centerX + dot.x * scale;
    var y = centerY + dot.y * scale;
    return { x: x, y: y };
  }

  window.onmousewheel = function(e) {
    cubeList.forEach(function(cube) {
      var nx = cube.xpos / 10;
      var ny = cube.ypos / 10;
      var nz = cube.zpos / 10;

      nx = e.wheelDelta > 0 ? nx : -nx;
      ny = e.wheelDelta > 0 ? ny : -ny;
      nz = e.wheelDelta > 0 ? nz : -nz;

      cube.move(nx, ny, nz);
    })
  };

  //鼠标事件绑定
  canvas.onmousedown = function(e) {
    var angleY, angleX;

    e = e || window.event;
    var x1 = e.clientX - canvas.offsetLeft - centerX;
    var y1 = e.clientY - canvas.offsetTop - centerY;

    var xc = 0, yc = 0;
    var date = new Date();
    var dc = 10000;

    cubeList.forEach(function(cube) {
      cube.inertia = false;
    });

    window.onmousemove = function(e) {
      e = e || window.event;
      var x2 = e.clientX - canvas.offsetLeft - centerX;
      var y2 = e.clientY - canvas.offsetTop - centerY;
      var date2 = new Date();

      xc = x2 - x1;
      yc = y2 - y1;

      angleY = xc * 0.005;
      angleX = yc * 0.005;

      cubeList.forEach(function(cube) {
        cube.rotate(angleX, angleY);
      });

      x1 = x2;
      y1 = y2;
      date = date2;
    };

    window.mouseleave = window.onmouseup = function(e) {
      window.onmousemove = null;
      var date2 = new Date();
      var dc = (date2 - date) || 1;

      cubeList.forEach(function(cube) {
        cube.inertia = true;
        cube.toAngle = {
          x: yc / dc,
          y: xc / dc
        };
      });
    }
  };

  /**
   * 动画初始化
   */
  var cubeList = [],
      cubeFace = [],
      distance = 200,
      level = 3,
      cube_w = 100;
  function initAnimate() {
    var cw = cube_w / level;
    var dis = cw + distance;
    var minmax = (level - 1) * dis;

    var cube;

    //三层正方体
    for (var l1 = -minmax; l1 <= minmax; l1 += dis) {
      for (var l2 = -minmax; l2 <= minmax; l2 += dis) {
        for (var l3 = -minmax; l3 <= minmax; l3 += dis) {
          cube = new Cube({ x: l1, y: l2, z: l3, w: cw });
          cubeFace = cubeFace.concat(cube.faces);
          cubeList.push(cube);

          cube.inertia = true;
          cube.toAngle = {
            x: 2,
            y: 10
          }
        }
      }
    }

    animate();
  }

  function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    cubeFace.sort(function(a, b) {
      var indexa = (a[0].z + a[1].z + a[2].z + a[3].z) / 4;
      var indexb = (b[0].z + b[1].z + b[2].z + b[3].z) / 4;

      return indexb - indexa;
    });

    //遍历cube判断是否需要旋转
    cubeList.forEach(function(cube) {
      if (cube.inertia) {
        var ax = cube.toAngle.x * 0.02;
        var ay = cube.toAngle.y * 0.02;

        cube.toAngle.x -= ax;
        cube.toAngle.y -= ay;

        cube.rotate(ax, ay);
      }
    });

    cubeFace.forEach(function(face) {
      drawFace(face);
    });

    stats.update();

    RAF(animate);
  }

  function getRandom(a, b) {
    return Math.random() * (b - a) + a;
  }

  initAnimate();
</script>
</body>
</html>